/**
 * ToDo: Separate out the tokenizer so we have one that does exactly what
 * we want
 */
import java.io.Reader;
import java.io.LineNumberReader;
import java.io.StreamTokenizer;
import java.io.IOException;
import java.util.*;

// import MyTokenizer;
// import ASTNode;

/**
 * The Parser class parses our language and builds the appropriate abstract
 * syntax tree.
 */
public class Parser {

    /** the input stream we read from */
    protected LineNumberReader _reader;

    /** 
     * Create a new Calculator parser that will read from the given Reader
     */
    public Parser(Reader r) {
	_reader = new LineNumberReader(r);
    }

    /**
     * Parse the given stream to make the abstract syntax tree
     */
    public ASTNode parse() throws IOException, ParseException {
	// set up a tokenizer on our input stream
	MyTokenizer st = new MyTokenizer(_reader);

	// create the abstract syntax tree
	ASTNode ast = parseExpressionList(st);

	// check that we parsed everything
	if (st.ttype != MyTokenizer.TT_EOF) {
	    throw new ParseException("Trailing garbage", 
				     _reader.getLineNumber());
	}
	return ast;
    }

    /**
     * Parse an expression list
     * <expression-list> -> <expression>
     * <expression-list> -> ';' <expression-list>
     * <expression-list> -> <block> <expression-list>
     * <expression-list> -> <expression> <expression-list>
     */
    protected ASTNode parseExpressionList(MyTokenizer st) 
	throws IOException, ParseException {
	ASTBlockNode n = new ASTBlockNode();
	while (1 < 2) { // forever 
	    // lookahead by one symbol
	    st.nextToken();
	    switch (st.ttype) {
	    case MyTokenizer.TT_LBRACE:
		st.pushBack();
		n.addExpression(parseBlock(st));
		break;
	    case MyTokenizer.TT_RBRACE:
		st.pushBack();
		return n;
	    case MyTokenizer.TT_EOF:
		return n;
	    case MyTokenizer.TT_SEMICOLON:
		continue;
	    default:
		st.pushBack();
		n.addExpression(parseExpression(st));
	    }
	}
    }

    /** 
     * Parse a block of code
     * <block> -> '{' <expression-list>  '}'
     */
    protected ASTNode parseBlock(MyTokenizer st) 
	throws IOException, ParseException {
	st.nextToken();
	if (st.ttype != MyTokenizer.TT_LBRACE) {
	    throw new ParseException("Expected '{', didn't get it",
				     _reader.getLineNumber());
	}
	ASTNode n = parseExpressionList(st);
	st.nextToken();
	if (st.ttype != MyTokenizer.TT_RBRACE) {
	    throw new ParseException("Expected '}', didn't get it",
				     _reader.getLineNumber());
	}
	return n;
    }

    
    /**
     * Parse an expression according to the rules:
     * <expression> -> func ( <expression> )
     * <expression> -> func ( <expression> ) <op> <expression>
     * <expression> -> ( <expression> )
     * <expression> -> ( <expression> ) <op> <expression>
     * <expression> -> <number> <op> <expression>
     * <expression> -> <number>
     */
    protected ASTNode parseExpression(MyTokenizer st)  
	throws IOException, ParseException {
	ASTNode n1 = null;
	//int prev_tType = 0;
	ASTOperatorNode n = null;
	// first token must be an open parenthesis, a function or a number
	st.nextToken();
	switch (st.ttype) {
	case MyTokenizer.TT_VARIABLE:
	    n1 = new ASTVariableNode(st.sval);
	    break;
	case MyTokenizer.TT_STRING:
	    n1 = new ASTConstNode(new StringData(st.sval));
	    break;
	case MyTokenizer.TT_LPAREN:
	    // this is an expression in parentheses 
	    st.pushBack();
	    n1 = parseParenExpression(st);
	    break;
	case MyTokenizer.TT_SIN:
	    n = new ASTOperatorNode(new Sin());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_COS:
	    n = new ASTOperatorNode(new Cos());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_TAN:
	    n = new ASTOperatorNode(new Tan());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_LOG:
	    n = new ASTOperatorNode(new Log());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_LN:
	    n = new ASTOperatorNode(new Ln());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_SQRT:
	    n = new ASTOperatorNode(new Sqrt());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_PRINT:
	    n = new ASTOperatorNode(new Print());
	    n.setChild(0, parseParenExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_E:
	    n1 = new ASTConstNode(new NumberData(Math.E));
	    break;
	case MyTokenizer.TT_PI:
	    n1 = new ASTConstNode(new NumberData(Math.PI));
	    break;
	case MyTokenizer.TT_PHI:
	    n1 = new ASTConstNode(new NumberData(Phi.value));
	    break;
	case MyTokenizer.TT_NUMBER:
	    n1 = new ASTConstNode(new NumberData(st.nval));
	    break;
	case MyTokenizer.TT_WHILE:
	    st.pushBack();
	    return parseWhileExpression(st);
	case MyTokenizer.TT_TRUE:
	    n1 = new ASTConstNode(new BooleanData(true));
	    break;
	case MyTokenizer.TT_FALSE:
	    n1 = new ASTConstNode(new BooleanData(false));
	    break;
	case MyTokenizer.TT_NUMBOOL:
		n = new ASTOperatorNode(new NumBool());
	    n.setChild(0, parseExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_NOT:
		n = new ASTOperatorNode(new Not()); 
	    n.setChild(0, parseExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_MINUS:
		if(st.nextToken() == MyTokenizer.TT_MINUS){
			//st.nextToken();
			if(st.nextToken() == MyTokenizer.TT_MINUS) //does not allow more than 2 "-" signs
			{
				throw new ParseException("Invalid argument to DECREMENT operation",_reader.getLineNumber());
			}
			st.pushBack();
			n = new ASTOperatorNode(new Decrement());
			n.setChild(0, parseExpression(st));
			n1 = n;
			
			
			
			//prev_tType = st.ttype;
			//break;
			return n1;
		}
		
		n = new ASTOperatorNode(new Negate());  
	    n.setChild(0, parseExpression(st));
	    n1 = n;
	    break;
	case MyTokenizer.TT_IF:
	    st.pushBack();
	    return parseIfExpression(st);
	case MyTokenizer.TT_PLUS:
		
		if(st.nextToken() == MyTokenizer.TT_PLUS){
			if(st.nextToken() == MyTokenizer.TT_PLUS) //does not allow more than 2 plus signs
			{
				throw new ParseException("Invalid argument to increment operation",_reader.getLineNumber());
			}
			st.pushBack();
			n = new ASTOperatorNode(new Increment());
			n.setChild(0, parseExpression(st));
			n1 = n;
			
			
			
			//prev_tType = st.ttype;
			//break;
			return n1;
		}
		break;
	case MyTokenizer.TT_MAX:
		st.pushBack();
		return parseMaxFunction(st);
		
	case MyTokenizer.TT_MIN:
		st.pushBack();
		return parseMinFunction(st);
		
	case  MyTokenizer.TT_COMMA:
		//st.nextToken();
		
		//throw new ParseException("FOUND A COMMA", _reader.getLineNumber());
		return parseExpression(st);
	
	default:
	    throw new ParseException("Expecting expression HERE, "
				     + "got something else: "+st.sval + "sttype:" + st.ttype, _reader.getLineNumber());
	}
	
	// read the operator (if any)
	st.nextToken();
	ASTOperatorNode n2 = null;
	ASTNode n4,n5,n6 = null;
	ASTChoiceNode n3 = null;
	switch (st.ttype) {
	case MyTokenizer.TT_ASSIGN:
	    if (!(n1 instanceof ASTVariableNode)) {
		throw new ParseException("Need variable on left hand side of =",
					 _reader.getLineNumber());
	    }
	    return new ASTAssignNode(((ASTVariableNode)n1).getName(), 
				   parseExpression(st));
	case MyTokenizer.TT_PLUS:
		if(st.nextToken() == MyTokenizer.TT_PLUS){
			if(st.nextToken() == MyTokenizer.TT_PLUS) //does not allow more than 2 plus signs
			{
				throw new ParseException("Invalid argument to increment operation",_reader.getLineNumber());
			}
			st.pushBack();
			n = new ASTOperatorNode(new Increment_post());
			n.setChild(0, n1);
			n2 = n;
			
			return n2;
			
			
			
			//prev_tType = st.ttype;
			//break;
		}
	    n2 = new ASTOperatorNode(new Plus());
	    break;
	case MyTokenizer.TT_MINUS:
		if(st.nextToken() == MyTokenizer.TT_MINUS){
			//st.nextToken();
			if(st.nextToken() == MyTokenizer.TT_MINUS) //does not allow more than 2 "-" signs
			{
				throw new ParseException("Invalid argument to DECREMENT operation",_reader.getLineNumber());
			}
			st.pushBack();
			n = new ASTOperatorNode(new Decrement_post());
			n.setChild(0, n1);
			n2 = n;
		
			return n2;
		}
		
	    n2 = new ASTOperatorNode(new Minus());
	    break;
	case MyTokenizer.TT_TIMES:
		if(st.nextToken() == MyTokenizer.TT_TIMES )
		{
			int count = 2;
			st.nextToken();
			while(st.ttype == MyTokenizer.TT_TIMES){
				count++;
				st.nextToken();
			}
			
			n2 = new ASTOperatorNode(new Power());
			n2.setChild(0, n1);
			n2.setChild(1, new ASTConstNode(new NumberData(count)));
			return n2;
		}
		
		st.pushBack();
		
		
	    n2 = new ASTOperatorNode(new Times());
	    break;
	case MyTokenizer.TT_DIVIDE:
	    n2 = new ASTOperatorNode(new Divide());
	    break;
	case MyTokenizer.TT_POWER:
	    n2 = new ASTOperatorNode(new Power());
	    break;
	case MyTokenizer.TT_LESSTHAN:
	    n2 = new ASTOperatorNode(new LessThan());
	    break;
	case MyTokenizer.TT_GREATERTHAN:
	    n2 = new ASTOperatorNode(new GreaterThan());
	    break;
	case MyTokenizer.TT_MODULUS:
	    n2 = new ASTOperatorNode(new Modulus());
	    break;
	case MyTokenizer.TT_AND:
		n2 = new ASTOperatorNode(new And());
	    break;
	case MyTokenizer.TT_OR:
		n2 = new ASTOperatorNode(new Or());
	    break;
	
		//parseExpression(st);

	
		
	case MyTokenizer.TT_CHOICE:
		n3 = new ASTChoiceNode(new Choice());
		n4 = parseExpression(st);//ChoiceOpt() operator {return args[0].value}
		if(st.nextToken()!=MyTokenizer.TT_CHOICEOPT)
			throw new ParseException("Expecting a colon, got " + st.sval,_reader.getLineNumber());
		n5 = parseExpression(st);			
		n3.setChild(0, n1);
		n3.setChild(1, n4);
		n3.setChild(2, n5);
		//n3.setChild(3, n6);
		return n3;
	case MyTokenizer.TT_COMMA:
		//throw new ParseException("GOT A COMMA",_reader.getLineNumber());
		return n1;
	
	default:
	    // this isn't a binary operator, push it back
	    st.pushBack();
	    return n1;
	}
	
	// parse the rest of the expression
	
	
	
	n2.setChild(0, n1);
	n2.setChild(1, parseExpression(st));
	return n2;
    }
    
    /**
     * Parse an expression enclosed in parentheses
     */
    protected ASTNode parseParenExpression(MyTokenizer st)
	throws IOException, ParseException {
	st.nextToken();
	if (st.ttype != MyTokenizer.TT_LPAREN) {  // open parenthesis '('
	    throw new ParseException("Expected '(', didn't get it",
				     _reader.getLineNumber());
	}
	ASTNode n = parseExpression(st);
	st.nextToken();
	if (st.ttype != MyTokenizer.TT_RPAREN) { // close parenthesis ')'
	    throw new ParseException("Unmatched parenthesis", 
				     _reader.getLineNumber());
	}
	return n;
    }

    /**
     * Parse a while expression
     * while-expression -> while ( <expression> ) <block>
     */
    protected ASTNode parseWhileExpression(MyTokenizer st) 
	throws IOException, ParseException {
	if (st.nextToken() != MyTokenizer.TT_WHILE) {
	    throw new ParseException("Expected while, didn't get it",
				 _reader.getLineNumber());
	}
	ASTNode cond = parseParenExpression(st);
	ASTNode res = parseBlock(st);
	return new ASTWhileNode(cond, res);
    }
    
    
    protected ASTNode parseMaxFunction(MyTokenizer st)
    throws IOException, ParseException {
    	
    	if (st.nextToken() != MyTokenizer.TT_MAX) {
    	    throw new ParseException("Expected min, didn't get it"+st.sval, _reader.getLineNumber());
    	}
    	//throw new ParseException("This is what I get" + st.ttype, _reader.getLineNumber());
    	int count = 0;
    	ArrayList a = new ArrayList();
    	
    	
    	
    	
    	if (st.nextToken() == MyTokenizer.TT_LPAREN){ 
    		st.nextToken();
    		//MyTokenizer k = st; 
    		
    		
    		//throw new ParseException("Expected '(', didn't get it"+st.sval, _reader.getLineNumber()); 
    		while (st.ttype != MyTokenizer.TT_RPAREN){
    			
    			if (st.ttype == MyTokenizer.TT_COMMA){
    				if (st.nextToken() == MyTokenizer.TT_COMMA){
    					throw new ParseException("expected a number",_reader.getLineNumber());
    				}
    			}
    			
    			
    			
        		if (st.ttype != MyTokenizer.TT_COMMA) {
        			if (st.ttype != MyTokenizer.TT_NUMBER){
        				throw new ParseException("Illegal arguments: expected a number got something else:  ", _reader.getLineNumber());
        			}
        			a.add(st.nval);
        			count++;
        		}
        			
        		st.nextToken();
        		
        			
        		
        	}
    		
    		if (count<2) throw new ParseException("max function takes 2 or more number arguments",_reader.getLineNumber());
    		
    		ASTOperatorNode n = new ASTOperatorNode(new Max(a));
    		
    		for (int i =0; i<a.size()-1;i++){
    			
    			n.setChild(i,new ASTConstNode( new NumberData((Double)a.get(i))     )     );
    		}
    		
    		
    		
    		return n;
    		
    		
    	}
		throw new ParseException("Expected an opening brace", _reader.getLineNumber());
    	
    	
    	
    	
    }
    
    
    /**----------------------------------------------------------------**/
    
    		
    protected ASTNode parseMinFunction(MyTokenizer st)
    throws IOException, ParseException {
    	if (st.nextToken() != MyTokenizer.TT_MIN) {
    	    throw new ParseException("Expected min, didn't get it"+st.sval, _reader.getLineNumber());
    	}
    	//throw new ParseException("This is what I get" + st.ttype, _reader.getLineNumber());
    	int count = 0;
    	ArrayList a = new ArrayList();
    	
    	
    	
    	
    	if (st.nextToken() == MyTokenizer.TT_LPAREN){ 
    		st.nextToken();
    		//MyTokenizer k = st; 
    		
    		
    		//throw new ParseException("Expected '(', didn't get it"+st.sval, _reader.getLineNumber()); 
    		while (st.ttype != MyTokenizer.TT_RPAREN){
    			
    			if (st.ttype == MyTokenizer.TT_COMMA){
    				if (st.nextToken() == MyTokenizer.TT_COMMA){
    					throw new ParseException("expected a number",_reader.getLineNumber());
    				}
    			}
    			
    			
    			
        		if (st.ttype != MyTokenizer.TT_COMMA) {
        			if (st.ttype != MyTokenizer.TT_NUMBER){
        				throw new ParseException("Illegal arguments: expected a number got something else:  ", _reader.getLineNumber());
        			}
        			a.add(st.nval);
        			count++;
        		}
        			
        		st.nextToken();
        		
        			
        		
        	}
    		
    		if (count<2) throw new ParseException("min function takes 2 or more number arguments",_reader.getLineNumber());
    		
    		ASTOperatorNode n = new ASTOperatorNode(new Min(a));
    		
    		for (int i =0; i<a.size()-1;i++){
    			
    			n.setChild(i,new ASTConstNode( new NumberData((Double)a.get(i))     )     );
    		}
    		
    		
    		
    		return n;
    		
    		
    	}
		throw new ParseException("Expected an opening brace", _reader.getLineNumber());
    	
    	
    	
    	
    	
    	
    }
    
    
    /**
     * Parse an if expression
     * 
     */
    protected ASTNode parseIfExpression(MyTokenizer st) 
	throws IOException, ParseException {
	if (st.nextToken() != MyTokenizer.TT_IF) {
	    throw new ParseException("Expected if, didn't get it",
				 _reader.getLineNumber());
	}
	ASTNode cond = parseParenExpression(st);
	ASTNode res = parseBlock(st);
	return new ASTIfNode(cond, res);
    }

}
